﻿
namespace Microsoft.Samples.Kinect.SkeletonBasics
{
    using System.Collections.Generic;
    using System.IO;
    using System.Windows;
    using System.Windows.Media;
    using Microsoft.Kinect;
    using System;
    using System.Media;
    using System.Threading.Tasks;
    using System.Windows.Controls;
    using System.Windows.Threading;
    using Telerik.Windows.Controls;
    using Telerik.Windows.Documents.FormatProviders.Txt;

    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        /// <summary>
        /// Width of output drawing
        /// </summary>
        private const float RenderWidth = 640.0f;

        /// <summary>
        /// Height of our output drawing
        /// </summary>
        private const float RenderHeight = 640.0f;
        
        /// <summary>
        /// Thickness of drawn joint lines
        /// </summary>
        private const double JointThickness = 3;

        /// <summary>
        /// Thickness of body center ellipse
        /// </summary>
        private const double BodyCenterThickness = 10;

        /// <summary>
        /// Thickness of clip edge rectangles
        /// </summary>
        private const double ClipBoundsThickness = 10;

        /// <summary>
        /// Brush used to draw skeleton center point
        /// </summary>
        private readonly Brush centerPointBrush = Brushes.Blue;

        /// <summary>
        /// Brush used for drawing joints that are currently tracked
        /// </summary>
        private readonly Brush trackedJointBrush = new SolidColorBrush(Color.FromArgb(255, 68, 192, 68));

        /// <summary>
        /// Brush used for drawing joints that are currently inferred
        /// </summary>        
        private readonly Brush inferredJointBrush = Brushes.Yellow;

        /// <summary>
        /// Pen used for drawing bones that are currently tracked
        /// </summary>
        private readonly Pen trackedBonePen = new Pen(Brushes.Green, 30);

        /// <summary>
        /// Pen used for drawing bones that are currently inferred
        /// </summary>        
        private readonly Pen inferredBonePen = new Pen(Brushes.Gray, 1);

        /// <summary>
        /// Active Kinect sensor
        /// </summary>
        private KinectSensor sensor;

        /// <summary>
        /// Drawing group for skeleton rendering output
        /// </summary>
        private DrawingGroup drawingGroup;

        /// <summary>
        /// Drawing image that we will display
        /// </summary>
        private DrawingImage imageSource;

        //Current skeleton type: Head, Arm...
        private JointType currentType = JointType.HandRight;

        //Current skeleton's position
        private Point skeletonPoint = new Point(0,0);

        private const int backgroundCellsStatusNumRows = 16;
        private const int backgroundCellsStatusNumCollums = 16;
        private const int backgroundCellSize = 40;
        private Brush[] burshList = { Brushes.Green, Brushes.Yellow, Brushes.Purple };
        private Brush backgroundBrush = Brushes.Gray;
        private Brush backgroundBoomBrush = Brushes.Red;
        private JointType[] enableSkeletons = { JointType.HandLeft, JointType.HandRight, JointType.Head};
        private int skeletonPointSize = 20;
        Random randomGenerator = new Random();
        System.Windows.Media.Imaging.BitmapImage bombImage, backgroundImage;
        System.Windows.Media.Imaging.BitmapImage[] dirtImages = {null, null, null,null};
        System.Windows.Media.Imaging.BitmapImage[] broomImages = { null, null, null };
        WMPLib.WindowsMediaPlayer backgroundSound, bombSound, bombAppearSound, timeruningoutSound;
        WMPLib.WindowsMediaPlayer[] tickSounds = { null, null, null, null, null };
        private int random_dirt_factor = 0;
        private int tickSoundIndex = 0;

        private bool[,] backgroundCellsStatus = new bool[backgroundCellsStatusNumRows, backgroundCellsStatusNumCollums];
        private bool[,] backgroundBoomStatus =  new bool[backgroundCellsStatusNumRows, backgroundCellsStatusNumCollums];

        enum GameStates {STOPED, PLAYING};

        private GameStates currentGameSTate = GameStates.STOPED;

        // This is for timer
        private DispatcherTimer _timer;
        private TimeSpan _time;
        private const int PlayTime = 60;

        /// <summary>
        /// Initializes a new instance of the MainWindow class.
        /// </summary>
        public MainWindow()
        {
            InitializeComponent();

            
            
        }

        /// <summary>
        /// Draws indicators to show which edges are clipping skeleton data
        /// </summary>
        /// <param name="skeleton">skeleton to draw clipping information for</param>
        /// <param name="drawingContext">drawing context to draw to</param>
        private static void RenderClippedEdges(Skeleton skeleton, DrawingContext drawingContext)
        {
            if (skeleton.ClippedEdges.HasFlag(FrameEdges.Bottom))
            {
                drawingContext.DrawRectangle(
                    Brushes.Red,
                    null,
                    new Rect(0, RenderHeight - ClipBoundsThickness, RenderWidth, ClipBoundsThickness));
            }

            if (skeleton.ClippedEdges.HasFlag(FrameEdges.Top))
            {
                drawingContext.DrawRectangle(
                    Brushes.Red,
                    null,
                    new Rect(0, 0, RenderWidth, ClipBoundsThickness));
            }

            if (skeleton.ClippedEdges.HasFlag(FrameEdges.Left))
            {
                drawingContext.DrawRectangle(
                    Brushes.Red,
                    null,
                    new Rect(0, 0, ClipBoundsThickness, RenderHeight));
            }

            if (skeleton.ClippedEdges.HasFlag(FrameEdges.Right))
            {
                drawingContext.DrawRectangle(
                    Brushes.Red,
                    null,
                    new Rect(RenderWidth - ClipBoundsThickness, 0, ClipBoundsThickness, RenderHeight));
            }
        }

        /// <summary>
        /// Execute startup tasks
        /// </summary>
        /// <param name="sender">object sending the event</param>
        /// <param name="e">event arguments</param>
        private void WindowLoaded(object sender, RoutedEventArgs e)
        {
            // Create the drawing group we'll use for drawing
            this.drawingGroup = new DrawingGroup();

            // Create an image source that we can use in our image control
            this.imageSource = new DrawingImage(this.drawingGroup);

            // Display the drawing using our image control
            Image.Source = this.imageSource;

            // Look through all sensors and start the first connected one.
            // This requires that a Kinect is connected at the time of app startup.
            // To make your app robust against plug/unplug, 
            // it is recommended to use KinectSensorChooser provided in Microsoft.Kinect.Toolkit (See components in Toolkit Browser).
            foreach (var potentialSensor in KinectSensor.KinectSensors)
            {
                if (potentialSensor.Status == KinectStatus.Connected)
                {
                    this.sensor = potentialSensor;
                    break;
                }
            }

            if (null != this.sensor)
            {
                // Turn on the skeleton stream to receive skeleton frames
                this.sensor.SkeletonStream.Enable();

                // Add an event handler to be called whenever there is new color frame data
                this.sensor.SkeletonFrameReady += this.SensorSkeletonFrameReady;

                // Start the sensor!
                try
                {
                    this.sensor.Start();
                }
                catch (IOException)
                {
                    this.sensor = null;
                }
            }

            if (null == this.sensor)
            {
                this.statusBarText.Text = Properties.Resources.NoKinectReady;
            }
            Init();
        }

        /// <summary>
        /// Execute shutdown tasks
        /// </summary>
        /// <param name="sender">object sending the event</param>
        /// <param name="e">event arguments</param>
        private void WindowClosing(object sender, System.ComponentModel.CancelEventArgs e)
        {
            if (null != this.sensor)
            {
                this.sensor.Stop();
            }
        }

        /// <summary>
        /// Event handler for Kinect sensor's SkeletonFrameReady event
        /// </summary>
        /// <param name="sender">object sending the event</param>
        /// <param name="e">event arguments</param>
        private void SensorSkeletonFrameReady(object sender, SkeletonFrameReadyEventArgs e)
        {
            Skeleton[] skeletons = new Skeleton[0];

            using (SkeletonFrame skeletonFrame = e.OpenSkeletonFrame())
            {
                if (skeletonFrame != null)
                {
                    skeletons = new Skeleton[skeletonFrame.SkeletonArrayLength];
                    skeletonFrame.CopySkeletonDataTo(skeletons);
                }
            }

            using (DrawingContext dc = this.drawingGroup.Open())
            {
                // Draw a transparent background to set the render size
                //dc.DrawRectangle(Brushes.Black, null, new Rect(0.0, 0.0, RenderWidth, RenderHeight));
                dc.DrawImage(backgroundImage, new Rect(0.0, 0.0, RenderWidth, RenderHeight));
                drawBackground(dc);

                if (skeletons.Length != 0)
                {
                    foreach (Skeleton skel in skeletons)
                    {
                        RenderClippedEdges(skel, dc);

                        if (skel.TrackingState == SkeletonTrackingState.Tracked)
                        {
                            this.DrawSkeleton(skel, dc);
                            updateBackgroundCellStatus(skel);
                            GenerateMoreCellsAndBooms();
                        }
                        else if (skel.TrackingState == SkeletonTrackingState.PositionOnly)
                        {
                            dc.DrawEllipse(
                            this.centerPointBrush,
                            null,
                            this.SkeletonPointToScreen(skel.Position),
                            BodyCenterThickness,
                            BodyCenterThickness);
                        }
                    }
                }
              
                // prevent drawing outside of our render area
                this.drawingGroup.ClipGeometry = new RectangleGeometry(new Rect(0.0, 0.0, RenderWidth, RenderHeight));
            }
        }

        /// <summary>
        /// Draws a skeleton's bones and joints
        /// </summary>
        /// <param name="skeleton">skeleton to draw</param>
        /// <param name="drawingContext">drawing context to draw to</param>
        private void DrawSkeleton(Skeleton skeleton, DrawingContext drawingContext)
        {
            if (currentGameSTate == GameStates.STOPED) return;
            Brush drawBrush = null;
            drawBrush = this.trackedJointBrush;
            for (int i = 0; i < enableSkeletons.Length; i++)
            {
                //drawingContext.DrawEllipse(burshList[i], null, this.SkeletonPointToScreen(skeleton.Joints[enableSkeletons[i]].Position), skeletonPointSize, skeletonPointSize);
                drawingContext.DrawImage(broomImages[i], new Rect(this.SkeletonPointToScreen(skeleton.Joints[enableSkeletons[i]].Position).X - 30, this.SkeletonPointToScreen(skeleton.Joints[enableSkeletons[i]].Position).Y - 30, 60, 60));
            }
            
           
        }

        /// <summary>
        /// Maps a SkeletonPoint to lie within our render space and converts to Point
        /// </summary>
        /// <param name="skelpoint">point to map</param>
        /// <returns>mapped point</returns>
        private Point SkeletonPointToScreen(SkeletonPoint skelpoint)
        {
            // Convert point to depth space.  
            // We are not using depth directly, but we do want the points in our 640x480 output resolution.
            DepthImagePoint depthPoint = this.sensor.CoordinateMapper.MapSkeletonPointToDepthPoint(skelpoint, DepthImageFormat.Resolution640x480Fps30);
            float y = (depthPoint.Y - 320 )* 2 + 320;
            float x = (depthPoint.X - 320 )* 2 + 320;
            if (y > 640) y = 640;
            if (y < 0) y = 0;
            if (x > 640) x = 640;
            if (x < 0) x = 0;
            return new Point(x,y);
        }

        /// <summary>
        /// Handles the checking or unchecking of the seated mode combo box
        /// </summary>
        /// <param name="sender">object sending the event</param>
        /// <param name="e">event arguments</param>

        private void updateBackgroundCellStatus(Skeleton skeleton)
        {
            if (currentGameSTate == GameStates.STOPED) return;
            foreach (JointType type in enableSkeletons)
            {
                Point point = this.SkeletonPointToScreen(skeleton.Joints[type].Position);
                int i = (int)point.X / backgroundCellSize;
                int j = (int)point.Y / backgroundCellSize;
                if (i >= 0 && i < backgroundCellsStatusNumRows && j >= 0 && j < backgroundCellsStatusNumCollums)
                {
                    if (backgroundCellsStatus[i, j] == true)
                    {
                        tickSoundIndex = (tickSoundIndex + 1) % 5;
                        tickSounds[tickSoundIndex].controls.stop();
                        tickSounds[tickSoundIndex].controls.play();                 
                    }
                    backgroundCellsStatus[i, j] = false;
                    if (backgroundBoomStatus[i, j] == true)
                    {
                        backgroundBoomStatus[i, j] = false;
                        bombSound.controls.play();
                        resetBackgroundCellsStatus();
                    }
                }
            }
            tb_score.Text = Convert.ToString(CountPoints());
        }

        private void drawBackground(DrawingContext drawingContext)
        {
             Brush drawBrush = null;
             drawBrush = inferredJointBrush;
             for (int i = 0; i < backgroundCellsStatusNumRows; i++)
            {
                for (int j = 0; j < backgroundCellsStatusNumCollums; j++)
                {
                    if (backgroundCellsStatus[i, j] == true)
                    {
                        int dirt_index = (i*i +  j + random_dirt_factor) % 3; 
                        drawingContext.DrawImage(dirtImages[dirt_index], new Rect(i * backgroundCellSize, j * backgroundCellSize, backgroundCellSize, backgroundCellSize));
                    }
                    if (backgroundBoomStatus[i, j] == true)
                    {
                        drawingContext.DrawImage(bombImage, new Rect(i * backgroundCellSize, j * backgroundCellSize, backgroundCellSize, backgroundCellSize));
                                           
                    }
                   
                }
            }
        }

        private void resetBackgroundCellsStatus()
        {
            for (int i = 0; i < backgroundCellsStatusNumRows; i++)
            {
                for (int j = 0; j < backgroundCellsStatusNumCollums; j++)
                {
                    backgroundCellsStatus[i,j] = true;
                }
            }
        }

        private void resetBackgroundBoomStatus()
        {
            for (int i = 0; i < backgroundCellsStatusNumRows; i++)
            {
                for (int j = 0; j < backgroundCellsStatusNumCollums; j++)
                {
                    backgroundBoomStatus[i, j] = false;
                }
            }
        }

        private void Init()
        {
            resetBackgroundCellsStatus();
            resetBackgroundBoomStatus();
            random_dirt_factor = randomGenerator.Next(10);
            bombImage = new System.Windows.Media.Imaging.BitmapImage(new Uri(@".\Images\bomb.png", UriKind.Relative));
            
            backgroundImage = new System.Windows.Media.Imaging.BitmapImage(new Uri(@".\Images\background.jpg", UriKind.Relative));
            dirtImages[0] = new System.Windows.Media.Imaging.BitmapImage(new Uri(@".\Images\dirt1.png", UriKind.Relative));
            dirtImages[1] = new System.Windows.Media.Imaging.BitmapImage(new Uri(@".\Images\dirt2.png", UriKind.Relative)); 
            dirtImages[2] = new System.Windows.Media.Imaging.BitmapImage(new Uri(@".\Images\dirt3.png", UriKind.Relative));
            dirtImages[3] = new System.Windows.Media.Imaging.BitmapImage(new Uri(@".\Images\dirt4.png", UriKind.Relative));

            broomImages[0] = new System.Windows.Media.Imaging.BitmapImage(new Uri(@".\Images\broom1.png", UriKind.Relative));
            broomImages[1] = new System.Windows.Media.Imaging.BitmapImage(new Uri(@".\Images\broom2.png", UriKind.Relative));
            broomImages[2] = new System.Windows.Media.Imaging.BitmapImage(new Uri(@".\Images\broom3.png", UriKind.Relative));

            backgroundSound = new WMPLib.WindowsMediaPlayer();
            backgroundSound.URL = @".\Sounds\background.mp3";
            bombSound = new WMPLib.WindowsMediaPlayer();
            bombSound.URL = @".\Sounds\bomb.mp3";
            bombSound.controls.stop();
          
            backgroundSound.settings.setMode("loop", true);
            backgroundSound.settings.volume = 50;
            backgroundSound.controls.stop();

            bombAppearSound = new WMPLib.WindowsMediaPlayer();
            bombAppearSound.URL = @".\Sounds\bombappear.mp3";
            bombAppearSound.settings.volume = 50;
            bombAppearSound.controls.stop();
           // StartGame();

            timeruningoutSound = new WMPLib.WindowsMediaPlayer();
            timeruningoutSound.URL = @".\Sounds\timeruningout.mp3";
            timeruningoutSound.controls.stop();
            for (int i = 0; i < 5; i++)
            {
                tickSounds[i] = new WMPLib.WindowsMediaPlayer();
                tickSounds[i].URL = @".\Sounds\tick.mp3";
                tickSounds[i].settings.volume = 50;
                tickSounds[i].controls.stop();
            }
               
        }

        private void PlaySoundBomb()
        {
            SoundPlayer bomb = new SoundPlayer(@".F\Sounds\bomb.wav");
            bomb.Play();
        }

        private void GenerateMoreCellsAndBooms()
        {
            if (currentGameSTate == GameStates.STOPED) return;
            for (int i = 0; i < backgroundCellsStatusNumRows; i++)
            {
                for (int j = 0; j < backgroundCellsStatusNumCollums; j++)
                {

                    //int cell = randomGenerator.Next(1, 50000);
                    //if (cell == 1)
                    //{
                    //    backgroundCellsStatus[i, j] = true;
                    //}


                    int boom = randomGenerator.Next(1, 200000);
                    if (boom == 1)
                    {
                        backgroundBoomStatus[i, j] = true;
                        bombAppearSound.controls.play();
                    }
                }
            }
        }

        private int CountPoints()
        {
            int points = 0;
            for (int i = 0; i < backgroundCellsStatusNumRows; i++)
            {
                for (int j = 0; j < backgroundCellsStatusNumCollums; j++)
                {
                    if (backgroundCellsStatus[i,j] == false)
                        points++;
                }
            }
            return points;
        }

        private void ChangeSkeketonType(JointType type)
        {
            currentType = type;
            InitNewGame();
        }
        
        private void InitNewGame(){
            List<JointType> list = new List<JointType>();
            if (chkbox_head.IsChecked.Value) list.Add(JointType.Head);
            if (chkbox_rhand.IsChecked.Value) list.Add(JointType.HandRight);
            if (chkbox_rleg.IsChecked.Value) list.Add(JointType.FootRight);
            if (chkbox_lhand.IsChecked.Value) list.Add(JointType.HandLeft);
            if (chkbox_lleg.IsChecked.Value) list.Add(JointType.FootLeft);
            enableSkeletons = list.ToArray();
            resetBackgroundBoomStatus();
            resetBackgroundCellsStatus();
        }
    
        private void StopGame(){
            currentGameSTate = GameStates.STOPED;
            backgroundSound.controls.pause();
            timeruningoutSound.controls.stop();
            TxtFormatProvider prov = new TxtFormatProvider();
            string content = prov.Export(tb_name.Document);
            if (content.Equals("")) content = "Noname";
            UpdateRanking(content, Convert.ToInt32(tb_score.Text)); 
        }

        private void StartGame()
        {
            InitNewGame();
            currentGameSTate = GameStates.PLAYING;
            backgroundSound.controls.play();
        }

        private void BtnStartClick(object sender, RoutedEventArgs e)
        {
            RadButton btn = (RadButton) sender;
            if (btn.Content.Equals("Start")) {
                StartGame();
                SetTimer();
                _timer.Start(); 
                btn.Content = "Stop";
            }
            else {
                StopGame();
                _timer.Stop();
                btn.Content = "Start";
                tb_countdown.Text = PlayTime.ToString();
                bar_time.Value = PlayTime;
                Color myColor = Color.FromArgb((byte)255, 0, 0, 255);
                grad1.Color = myColor;
                grad2.Color = myColor;
            }
        }

        private void SetTimer()
        {
            int red = 0;
            int blue = 255;
            _time = TimeSpan.FromSeconds(PlayTime);
            bar_time.Maximum = PlayTime;
            
            _timer = new DispatcherTimer(new TimeSpan(0, 0, 1), DispatcherPriority.Normal, delegate
            {
                _time = _time.Add(TimeSpan.FromSeconds(-1));
                tb_countdown.Text = _time.ToString("%s");
                //add time runing out sound
                if (_time.Seconds == 15)
                {
                    timeruningoutSound.controls.play();
                }
                //end
                //bar_time
                bar_time.Value = _time.Seconds;
                //changeColor
                red += (255 / PlayTime);
                blue -= (255 / PlayTime);
                if (red > 255) red = 255;
                if (blue < 0) blue = 0;
                //string hexRed = red.ToString("X");
                //string hexBlue = blue.ToString("X");
                //string color = "#" + hexRed + "00" + hexBlue;
                Color myColor = Color.FromArgb((byte)255, (byte)red, 0, (byte)blue);
                grad1.Color = myColor;
                grad2.Color = myColor;

                if (_time == TimeSpan.Zero)
                {
                    _timer.Stop();
                    bt_start.Content = "Start";
                    StopGame();
                }
            }, Application.Current.Dispatcher);
        }

        private void UpdateRanking(string name, int point)
        {
            int lastPoint = Convert.ToInt32(((RadListBoxItem)lb_point.Items[lb_point.Items.Count - 1]).Content);
            if (point > lastPoint)
            {
                bool firstPos = true;
                for (int i = lb_point.Items.Count - 1; i > 0; i--)
                {
                    int prevPoint = Convert.ToInt32(((RadListBoxItem)lb_point.Items[i - 1]).Content);
                    if (point > prevPoint)
                    {
                        ((RadListBoxItem)lb_point.Items[i]).Content = ((RadListBoxItem)lb_point.Items[i - 1]).Content;
                        ((RadListBoxItem)lb_name.Items[i]).Content = ((RadListBoxItem)lb_name.Items[i - 1]).Content;
                    }
                    else
                    {
                        firstPos = false;
                        ((RadListBoxItem)lb_point.Items[i]).Content = point;
                        ((RadListBoxItem)lb_name.Items[i]).Content = name;
                        break;
                    }
                }
                if (firstPos)
                {
                    ((RadListBoxItem)lb_point.Items[0]).Content = point;
                    ((RadListBoxItem)lb_name.Items[0]).Content = name;
                }
            }
        }
    }
}